import numpy as np


def sigmoid(x):
    """【函数】S型函数"""
    return (1 / (1 + np.exp(-x)))


def deriv_sigmoid(x):
    """【函数】S型函数的导数：f(x)·[1-f(x)]"""
    # fx = sigmoid(x)
    # y = fx*(1-fx)
    return (sigmoid(x) * (1-sigmoid(x)))


def mse_loss(y_ture: np.ndarray, y_pred: np.ndarray):
    """【函数】平均方差（MSE）损失"""
    mse = ((y_ture - y_pred)**2).mean()
    return mse


# 构建神经网络

# 权重，初始化赋随机值
w1 = abs(np.random.normal(0.5, 0.5))
if(w1 > 1):
    w1 = abs(w1 - 1)
w2 = abs(np.random.normal(0.5, 0.5))
if(w2 > 1):
    w2 = abs(w2 - 1)
w3 = abs(np.random.normal(0.5, 0.5))
if(w3 > 1):
    w3 = abs(w3 - 1)
w4 = abs(np.random.normal(0.5, 0.5))
if(w4 > 1):
    w4 = abs(w4 - 1)
w5 = abs(np.random.normal(0.5, 0.5))
if(w5 > 1):
    w5 = abs(w5 - 1)
w6 = abs(np.random.normal(0.5, 0.5))
if(w6 > 1):
    w6 = abs(w6 - 1)

# w1 = np.random.random()
# w2 = np.random.random()
# w3 = np.random.random()
# w4 = np.random.random()
# w5 = np.random.random()
# w6 = np.random.random()

w = [w1, w2, w3, w4, abs(w5), abs(w6)]

# 偏差，初始化赋随机值
b1 = np.random.normal()
b2 = np.random.normal()
b3 = np.random.normal()

b = [b1, b2, b3]


def feedforward(x: list, FP_w: list):
    """前馈计算"""
    hidden1 = sigmoid(x[0] * w1 + x[1] * w2 + b1)
    hidden2 = sigmoid(x[0] * w3 + x[1] * w4 + b2)
    output = sigmoid(hidden1 * w5 + hidden2 * w6 + b3)
    return output


def train(data, y_trues, FP_w: list, FP_b: list):
    """"""
    learn_rate = 0.1
    epochs = 100

    w1 = FP_w[0]
    w2 = FP_w[1]
    w3 = FP_w[2]
    w4 = FP_w[3]
    w5 = FP_w[4]
    w6 = FP_w[5]

    b1 = FP_b[0]
    b2 = FP_b[1]
    b3 = FP_b[2]


    for epoch in range(epochs):
        for (x, y_true) in zip(data, y_trues):
            # 前馈计算
            h1 = sigmoid(x[0] * w1 + x[1] * w2 + b1)
            h2 = sigmoid(x[0] * w3 + x[1] * w4 + b2)
            opt = sigmoid(h1 * w5 + h2 * w6 + b3)   # 相当于文中的y_pred

            # 计算偏导数
            p_L_p_opt = -2 * (y_true - opt)

            # h1
            p_opt_p_h1 = w5 * deriv_sigmoid(h1 * w5 + h2 * w6 + b3)
            p_h1_p_w1 = x[0] * deriv_sigmoid(x[0] * w1 + x[1] * w2 + b1)
            p_h1_p_w2 = x[1] * deriv_sigmoid(x[0] * w1 + x[1] * w2 + b1)
            p_h1_p_b1 = deriv_sigmoid(x[0] * w1 + x[1] * w2 + b1)
            w1 = w1 - learn_rate * p_L_p_opt * p_opt_p_h1 * p_h1_p_w1   # 更新权重
            w2 = w2 - learn_rate * p_L_p_opt * p_opt_p_h1 * p_h1_p_w2
            b1 = b1 - learn_rate * p_L_p_opt * p_opt_p_h1 * p_h1_p_b1   # 更新偏差

            # h2
            p_opt_p_h2 = w6 * deriv_sigmoid(h1 * w5 + h2 * w6 + b3)
            p_h2_p_w3 = x[0] * deriv_sigmoid(x[0] * w3 + x[1] * w4 + b2)
            p_h2_p_w4 = x[1] * deriv_sigmoid(x[0] * w3 + x[1] * w4 + b2)
            p_h2_p_b2 = deriv_sigmoid(x[0] * w3 + x[1] * w4 + b2)
            w3 = w3 - learn_rate * p_L_p_opt * p_opt_p_h2 * p_h2_p_w3   # 更新权重
            w4 = w4 - learn_rate * p_L_p_opt * p_opt_p_h2 * p_h2_p_w4
            b2 = b2 - learn_rate * p_L_p_opt * p_opt_p_h1 * p_h2_p_b2   # 更新偏差

            # opt
            p_opt_p_w5 = h1 * deriv_sigmoid(h1 * w5 + h2 * w6 + b3)
            p_opt_p_w6 = h2 * deriv_sigmoid(h1 * w5 + h2 * w6 + b3)
            p_opt_p_b3 = deriv_sigmoid(h1 * w5 + h2 * w6 + b3)
            w5 = w5 - learn_rate * p_L_p_opt * p_opt_p_w5   # 更新权重
            w6 = w6 - learn_rate * p_L_p_opt * p_opt_p_w6
            b3 = b3 - learn_rate * p_L_p_opt * p_opt_p_b3   # 更新偏差

        if (epoch % 10 == 0):
            hidden1 = sigmoid(x[0] * w1 + x[1] * w2 + b1)
            hidden2 = sigmoid(x[0] * w3 + x[1] * w4 + b2)
            output = sigmoid(hidden1 * w5 + hidden2 * w6 + b3)
            # y_preds = np.apply_along_axis(feedforward, 1, data)
            y_preds = output
            loss = mse_loss(y_trues, y_preds)
            print("Epoch %d loss: %.3f" % (epoch, loss))
            w = [w1, w2, w3, w4, w5, w6]
            print(w)

    save_path = "./result-" + str(epoch) + ".txt"
    np.savetxt(save_path, w)
    return None


# 数据读取
data = np.loadtxt("data_train.txt", delimiter="\t", usecols=(0, 1, 2))
x_input = data[:, 0:2]
y_trues = data[:, 2]
print(data)
print(x_input)
print(y_trues)


train(data, y_trues, w, b)
